#include "ModelViewer.h"
#include "camera.h"
#include "MeshProg.h"
#include "Model3D.h"
#include <QOpenGLBuffer>
#include <QOpenGLFunctions_3_3_Core>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QDebug>
#include <assimp/scene.h>


ModelViewer::ModelViewer()
    : glFunc_(nullptr)
    , camera_(nullptr)
    , firstMouse_(true)
    , model_(nullptr)
{}

ModelViewer::~ModelViewer()
{
    reset();
}

QString ModelViewer::contextInformation() const
{
    QString glType;
    QString glVersion;
    QString glProfile;

    // Get Version Information
    glType = (context()->isOpenGLES()) ? "OpenGL ES" : "OpenGL";
    glVersion = reinterpret_cast<const char*>(glFunc_->glGetString(GL_VERSION));
  //  glVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));

    // Get Profile Information
  #define CASE(c) case QSurfaceFormat::c: glProfile = #c; break
    switch (format().profile())
    {
      CASE(NoProfile);
      CASE(CoreProfile);
      CASE(CompatibilityProfile);
    }
  #undef CASE

     return glType + "\t" + glVersion + "\t" + glProfile;
}

void ModelViewer::setModel(Model3D* m)
{
    if (model_ == m)
        return;

    model_ = m;

    if (model_) {
        connect(model_, SIGNAL(sceneChanged()), SLOT(reloadModel()));

        if (context())
            reloadModel();
        else
            clearProg();
    }
}

void ModelViewer::initializeGL()
{
    glFunc_ = context()->versionFunctions<QOpenGLFunctions_3_3_Core>();

    if (! glFunc_) {
        qCritical("NO support for QOpenGLFunctions_3_3_Core!");
        ::exit(0);
    }

    connect(context(), SIGNAL(aboutToBeDestroyed()), SLOT(reset()));
    glFunc_->initializeOpenGLFunctions();
    glFunc_->glEnable(GL_CULL_FACE);
    glFunc_->glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    camera_ = new Camera(QVector3D(0.0f, 0.0f, 3.0f));
}

void ModelViewer::paintGL()
{
    glFunc_->glClear(GL_COLOR_BUFFER_BIT);

    QMatrix4x4  projection;
    projection.perspective(camera_->fov, width() / float(height()), 0.1f, 100.0f);

    if (prog_.isEmpty() && model_)
        reloadModel();

    Light light;
    light.position = QVector3D(0.0f, 0.0f, 3.0f);
    light.ambient = QVector3D(0.1f, 0.1f, 0.1f);
    light.diffuse = QVector3D(0.9f, 0.9f, 0.9f);
    light.specular = QVector3D(0.0f, 0.0f, 0.0f);

    foreach (MeshProg* prog, prog_)
        prog->paint(*camera_, projection, light);
}

void ModelViewer::resizeGL(int w, int h)
{
    Q_UNUSED(w)
    Q_UNUSED(h)

    firstMouse_ = true;
}

void ModelViewer::keyPressEvent(QKeyEvent *ev)
{
    switch (ev->key()) {

    case Qt::Key_W:
        camera_->move(Camera::FORWARD);
        update();
        break;

    case Qt::Key_S:
        camera_->move(Camera::BACKWARD);
        update();
        break;

    case Qt::Key_A:
        camera_->move(Camera::LEFT);
        update();
        break;

    case Qt::Key_D:
        camera_->move(Camera::RIGHT);
        update();
        break;

    default:
        ev->ignore();
        break;
    }
}

void ModelViewer::mouseMoveEvent(QMouseEvent *ev)
{
    if (firstMouse_) {
        lastMousePos_ = ev->pos();
        firstMouse_ = false;
        return;
    }

    QPoint diff = ev->pos() - lastMousePos_;

    if (QPoint::dotProduct(diff, diff) > 8 * 8) {
        // ignore the case in which the mouse move drastically
        firstMouse_ = true;
        return;
    }

    float xoffset = ev->x() - lastMousePos_.x();
    float yoffset = lastMousePos_.y() - ev->y();

    lastMousePos_ = ev->pos();

    camera_->rotate(xoffset, yoffset);
    update();
}

void ModelViewer::wheelEvent(QWheelEvent *ev)
{
    if (ev->delta() > 0) {

        camera_->zoomIn();
        update();

    } else if (ev->delta() < 0) {

        camera_->zoomOut();
        update();

    } else
        ev->ignore();
}

void ModelViewer::reset()
{
    clearProg();
    model_ = nullptr;
    lastMousePos_ = QPoint(0, 0);
    firstMouse_ = true;

    if (camera_) {
        delete camera_;
        camera_ = nullptr;
    }

    glFunc_ = nullptr;
}

void ModelViewer::reloadModel()
{
    Q_ASSERT(model_);
    clearProg();

    const aiScene* s = model_->scene();

    if (s) {
        processNode(s->mRootNode);
        update();
    }
}

void ModelViewer::processNode(const aiNode *node)
{
    for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
        const aiMesh* mesh = model_->scene()->mMeshes[node->mMeshes[i]];
        prog_.push_back(processMesh(mesh));
    }

    for (unsigned int i = 0; i < node->mNumChildren; ++i) {
        processNode(node->mChildren[i]);
        //qDebug("%s: node name %s", Q_FUNC_INFO, node->mChildren[i]->mName.C_Str());
    }
}

MeshProg* ModelViewer::processMesh(const aiMesh* mesh)
{
    Q_ASSERT(context());

    MeshProg* prog = new MeshProg(this);
    prog->initialize(context(), mesh, model_->scene(), model_->dir());

    return prog;
}

void ModelViewer::clearProg()
{
    for (QVector<MeshProg*>::iterator i = prog_.begin(); i != prog_.end(); ++i)
        delete *i;

    prog_.clear();
}
